# -*- coding: utf-8 -*-
# @time:2025/4/4:21:33
# @IDE:PyCharm

import os
import sys
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
from PIL import Image, ImageTk
import json
import math

from ..scr.processors import (
    process_single_page,
    process_double_page,
    process_by_binding_simple,
    process_by_points_align_scale
)
from ..scr.utils import open_file, _save_image, _handle_processing_error
from ..scr.config import load_config, save_config


class GujipingyeApp:
    def __init__(self, root):
        self.root = root
        self.root.title("古籍拼页工具 V1.2 (支持采样点)")
        self.root.geometry("900x750")

        # 配置变量
        self.input_dir = tk.StringVar()
        self.output_dir = tk.StringVar()
        self.book_name = tk.StringVar(value="processed_book")
        self.book_format_options = ["依据装帧拼版", "采样点对齐拼版", "单页拼版", "双页拼版"]
        self.book_format = tk.StringVar(value="采样点对齐拼版")
        self.ground_height = tk.IntVar(value=0)
        self.dpi = tk.IntVar(value=300)
        self.margin_white = tk.IntVar(value=5)
        self.save_format = tk.StringVar(value="jpg")
        self.preview_image_index = tk.IntVar(value=0)
        self.show_processed_image = tk.BooleanVar(value=True)

        # 操作点位置 (相对 Y, 0.0 到 1.0)
        self.point_positions = [
            tk.DoubleVar(value=0.15),  # P1: 左上 Y
            tk.DoubleVar(value=0.85),  # P2: 左下 Y
            tk.DoubleVar(value=0.15),  # P3: 右上 Y
            tk.DoubleVar(value=0.85),  # P4: 右下 Y
        ]
        self.absolute_points = [(None, None)] * 4

        # 内部状态变量
        self.preview_canvas = None
        self.preview_photo_image = None
        self.preview_photo_image_left = None
        self.preview_photo_image_right = None
        self.image_files = []
        self.current_preview_pil_image = None
        self.current_preview_pair_pil_image_right = None
        self.preview_display_info = {}

        # 手动采样点相关状态
        self.is_sampling_mode = False
        self.current_point_to_set = 0
        self.sampling_instruction_label = None
        self.point_markers = []
        self.point_entries = []

        # UI 创建
        self.create_widgets()

        # 配置文件路径
        if getattr(sys, 'frozen', False):
            self.script_dir = os.path.dirname(sys.executable)
        else:
            self.script_dir = os.path.dirname(os.path.abspath(__file__))
        self.config_file = os.path.join(self.script_dir, "config_gujipingye_v1.2.json")
        load_config(self)

        self.root.bind("<Configure>", self.on_window_resize)
        self.root.after(100, self.refresh_preview_display)

    def create_widgets(self):
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        main_frame.rowconfigure(1, weight=1)
        main_frame.columnconfigure(0, weight=1)

        # 设置区域
        settings_outer_frame = ttk.Frame(main_frame)
        settings_outer_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))

        # 输入/输出路径
        io_frame = ttk.Frame(settings_outer_frame)
        io_frame.pack(fill=tk.X, pady=2)
        ttk.Label(io_frame, text="输入路径:").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Entry(io_frame, textvariable=self.input_dir, width=30).pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5)
        ttk.Button(io_frame, text="浏览...", command=self.select_input_dir).pack(side=tk.LEFT, padx=5)
        ttk.Label(io_frame, text="输出路径:").pack(side=tk.LEFT, padx=(10, 5))
        ttk.Entry(io_frame, textvariable=self.output_dir, width=30).pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5)
        ttk.Button(io_frame, text="浏览...", command=self.select_output_dir).pack(side=tk.LEFT, padx=5)

        # 格式/选项
        format_frame = ttk.Frame(settings_outer_frame)
        format_frame.pack(fill=tk.X, pady=2)
        ttk.Label(format_frame, text="拼版模式:").pack(side=tk.LEFT, padx=(0, 5))
        format_combo = ttk.Combobox(format_frame, textvariable=self.book_format,
                                    values=self.book_format_options, width=18, state="readonly")
        format_combo.pack(side=tk.LEFT, padx=5)
        format_combo.bind("<<ComboboxSelected>>", self.on_format_change)
        ttk.Label(format_frame, text="地脚高(px):").pack(side=tk.LEFT, padx=(10, 5))
        ttk.Entry(format_frame, textvariable=self.ground_height, width=5).pack(side=tk.LEFT, padx=5)
        ttk.Label(format_frame, text="中缝/右边距(px):").pack(side=tk.LEFT, padx=(10, 5))
        ttk.Entry(format_frame, textvariable=self.margin_white, width=5).pack(side=tk.LEFT, padx=5)
        ttk.Label(format_frame, text="DPI:").pack(side=tk.LEFT, padx=(10, 5))
        ttk.Entry(format_frame, textvariable=self.dpi, width=5).pack(side=tk.LEFT, padx=5)
        ttk.Label(format_frame, text="格式:").pack(side=tk.LEFT, padx=(10, 5))
        save_format_combo = ttk.Combobox(format_frame, textvariable=self.save_format,
                                         values=["jpg", "png", "tiff", "bmp"], width=5, state="readonly")
        save_format_combo.pack(side=tk.LEFT, padx=5)

        # 命名/预览索引
        name_prev_frame = ttk.Frame(settings_outer_frame)
        name_prev_frame.pack(fill=tk.X, pady=2)
        ttk.Label(name_prev_frame, text="输出书籍名:").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Entry(name_prev_frame, textvariable=self.book_name, width=20).pack(side=tk.LEFT, padx=5)
        ttk.Label(name_prev_frame, text="预览起始页(0=第一张):").pack(side=tk.LEFT, padx=(10, 5))
        ttk.Entry(name_prev_frame, textvariable=self.preview_image_index, width=5).pack(side=tk.LEFT, padx=5)
        ttk.Button(name_prev_frame, text="< 上一对", command=lambda: self.change_preview_page(-2)).pack(side=tk.LEFT, padx=(10, 2))
        ttk.Button(name_prev_frame, text="下一对 >", command=lambda: self.change_preview_page(2)).pack(side=tk.LEFT, padx=2)

        # 操作点
        self.points_frame = ttk.LabelFrame(settings_outer_frame, text="采样点 Y 坐标 (手动点击或输入)")
        self.points_frame.pack(fill=tk.X, pady=5)
        point_labels = ["P1(左上):", "P2(左下):", "P3(右上):", "P4(右下):"]
        for i, label in enumerate(point_labels):
            ttk.Label(self.points_frame, text=label).pack(side=tk.LEFT, padx=(5, 0))
            entry = ttk.Entry(self.points_frame, textvariable=self.point_positions[i], width=6)
            entry.pack(side=tk.LEFT, padx=(0, 10))
            self.point_entries.append(entry)
        self.start_sampling_button = ttk.Button(self.points_frame, text="开始手动采样", command=self.toggle_sampling_mode)
        self.start_sampling_button.pack(side=tk.LEFT, padx=10)
        self.clear_sampling_button = ttk.Button(self.points_frame, text="清除采样点", command=self.clear_sampling_points, state=tk.DISABLED)
        self.clear_sampling_button.pack(side=tk.LEFT, padx=5)

        # 采样提示
        self.sampling_instruction_label = ttk.Label(settings_outer_frame, text="", foreground="blue", font=("SimSun", 10, "bold"))
        self.sampling_instruction_label.pack(fill=tk.X, pady=(0, 5))

        # 操作按钮
        button_frame = ttk.Frame(settings_outer_frame)
        button_frame.pack(fill=tk.X, pady=5)
        ttk.Button(button_frame, text="加载配置", command=self.select_config_load).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="保存配置", command=self.select_config_save).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="刷新预览", command=self.preview).pack(side=tk.LEFT, padx=5)
        ttk.Checkbutton(button_frame, text="拼完不看图", variable=self.show_processed_image, onvalue=False, offvalue=True).pack(side=tk.LEFT, padx=(15, 5))
        ttk.Button(button_frame, text="开始拼版", command=self.start_processing, style="Accent.TButton").pack(side=tk.LEFT, padx=5)

        # 预览区域
        preview_area = ttk.LabelFrame(main_frame, text="预览区域 (点击采样)")
        preview_area.grid(row=1, column=0, sticky="nsew", pady=5)
        preview_area.rowconfigure(0, weight=1)
        preview_area.columnconfigure(0, weight=1)
        self.preview_canvas = tk.Canvas(preview_area, bg="#DDDDDD", relief=tk.SUNKEN, borderwidth=1)
        self.preview_canvas.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
        self.preview_canvas.bind("<Button-1>", self.on_canvas_click)

        # 状态栏
        status_frame = ttk.Frame(main_frame, relief=tk.SUNKEN, borderwidth=1)
        status_frame.grid(row=2, column=0, sticky="ew", pady=(5, 0))
        self.status_label = ttk.Label(status_frame, text="状态: 就绪 | V1.2 (支持采样点)")
        self.status_label.pack(side=tk.LEFT, padx=10, pady=2)
        ttk.Label(status_frame, text="永久开源").pack(side=tk.RIGHT, padx=10)

    def update_status(self, message):
        self.status_label.config(text=f"状态: {message}")
        self.root.update_idletasks()

    def on_window_resize(self, event=None):
        if hasattr(self, "_resize_job"):
            self.root.after_cancel(self._resize_job)
        self._resize_job = self.root.after(100, self.refresh_preview_display)

    def refresh_preview_display(self):
        if self.current_preview_pil_image:
            if self.book_format.get() != "单页拼版" and self.current_preview_pair_pil_image_right:
                self.display_preview_image_pair(self.current_preview_pil_image, self.current_preview_pair_pil_image_right)
            else:
                self.display_preview_image_single(self.current_preview_pil_image)
        else:
            self.preview_canvas.delete("all")
            self.preview_display_info = {}
            try:
                canvas_width = self.preview_canvas.winfo_width()
                canvas_height = self.preview_canvas.winfo_height()
                if canvas_width > 0 and canvas_height > 0:
                    self.preview_canvas.create_text(canvas_width / 2, canvas_height / 2, text="无预览图像", fill="gray")
            except tk.TclError:
                pass

    def on_format_change(self, event=None):
        self.refresh_preview_display()
        is_point_mode = (self.book_format.get() == "采样点对齐拼版")
        new_state = tk.NORMAL if is_point_mode else tk.DISABLED
        self.start_sampling_button.config(state=new_state)
        self.clear_sampling_button.config(state=new_state)
        for entry in self.point_entries:
            entry.config(state=new_state)
        if not is_point_mode and self.is_sampling_mode:
            self.toggle_sampling_mode(force_exit=True)

    def change_preview_page(self, delta):
        if not self.image_files:
            return
        current_index = self.preview_image_index.get()
        new_index = current_index + delta
        if new_index % 2 != 0:
            new_index = max(0, new_index - 1)
        if 0 <= new_index < len(self.image_files):
            self.preview_image_index.set(new_index)
            self.preview()
        elif new_index < 0:
            self.preview_image_index.set(0)
            if current_index != 0:
                self.preview()
        else:
            last_pair_start = (len(self.image_files) - 1) // 2 * 2
            self.preview_image_index.set(last_pair_start)
            if current_index != last_pair_start:
                self.preview()

    def select_input_dir(self):
        directory = filedialog.askdirectory(title="选择包含图片页面的文件夹")
        if directory:
            self.input_dir.set(directory)
            self.update_status(f"输入路径已选: ...{os.path.basename(directory)}")
            self.load_image_list()
            self.preview_image_index.set(0)
            self.preview()

    def select_output_dir(self):
        directory = filedialog.askdirectory(title="选择保存拼接后文件的文件夹")
        if directory:
            self.output_dir.set(directory)
            self.update_status(f"输出路径已选: ...{os.path.basename(directory)}")

    def load_image_list(self):
        self.image_files = []
        input_dir = self.input_dir.get()
        if not input_dir or not os.path.isdir(input_dir):
            self.update_status("输入路径无效或未设置。")
            return
        try:
            all_files = os.listdir(input_dir)
            self.image_files = sorted([f for f in all_files if f.lower().endswith((".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif"))])
            if not self.image_files:
                self.update_status("警告: 输入路径未找到支持的图片文件。")
            else:
                self.update_status(f"找到 {len(self.image_files)} 张图片。")
        except Exception as e:
            messagebox.showerror("读取目录错误", f"无法读取输入目录内容:\n{e}", parent=self.root)
            self.update_status("错误: 无法读取输入目录。")

    def select_config_load(self):
        file_path = filedialog.askopenfilename(title="选择配置文件", filetypes=[("JSON files", "*.json")], initialdir=self.script_dir)
        if file_path:
            load_config(self, file_path)

    def select_config_save(self):
        file_path = filedialog.asksaveasfilename(title="保存配置文件", defaultextension=".json", filetypes=[("JSON files", "*.json")],
                                                initialdir=self.script_dir, initialfile="config_gujipingye_v1.2.json")
        if file_path:
            save_config(self, file_path)

    def preview(self):
        if not self.image_files:
            self.load_image_list()
        if not self.image_files:
            self.preview_canvas.delete("all")
            self.current_preview_pil_image = None
            self.current_preview_pair_pil_image_right = None
            self.refresh_preview_display()
            self.update_status("预览: 无图片。")
            return

        self._clear_point_markers()
        try:
            preview_start_index = int(self.preview_image_index.get())
            if not (0 <= preview_start_index < len(self.image_files)):
                preview_start_index = 0
                self.preview_image_index.set(0)
            if preview_start_index % 2 != 0:
                preview_start_index = max(0, preview_start_index - 1)
                self.preview_image_index.set(preview_start_index)

            left_img_file = self.image_files[preview_start_index]
            left_img_path = os.path.join(self.input_dir.get(), left_img_file)
            if self.current_preview_pil_image:
                self.current_preview_pil_image.close()
            self.current_preview_pil_image = Image.open(left_img_path)
            self.current_preview_pil_image.load()

            if self.current_preview_pair_pil_image_right:
                self.current_preview_pair_pil_image_right.close()
            self.current_preview_pair_pil_image_right = None
            right_img_index = preview_start_index + 1
            if self.book_format.get() != "单页拼版" and right_img_index < len(self.image_files):
                right_img_file = self.image_files[right_img_index]
                right_img_path = os.path.join(self.input_dir.get(), right_img_file)
                self.current_preview_pair_pil_image_right = Image.open(right_img_path)
                self.current_preview_pair_pil_image_right.load()

            self.refresh_preview_display()
            if self.current_preview_pair_pil_image_right and right_img_index < len(self.image_files):
                self.update_status(f"预览 {preview_start_index} & {right_img_index}: {left_img_file}, {self.image_files[right_img_index]}")
            else:
                self.update_status(f"预览 {preview_start_index}: {left_img_file}")

        except FileNotFoundError as e:
            messagebox.showerror("预览错误", f"图片文件未找到:\n{e}", parent=self.root)
            self.update_status("预览错误: 文件未找到。")
            if self.current_preview_pil_image:
                self.current_preview_pil_image.close()
            if self.current_preview_pair_pil_image_right:
                self.current_preview_pair_pil_image_right.close()
            self.current_preview_pil_image = None
            self.current_preview_pair_pil_image_right = None
            self.preview_canvas.delete("all")
            self.refresh_preview_display()
        except Exception as e:
            messagebox.showerror("预览错误", f"无法加载或显示图片:\n{e}", parent=self.root)
            self.update_status(f"预览错误: {e}")
            if self.current_preview_pil_image:
                self.current_preview_pil_image.close()
            if self.current_preview_pair_pil_image_right:
                self.current_preview_pair_pil_image_right.close()
            self.current_preview_pil_image = None
            self.current_preview_pair_pil_image_right = None
            self.preview_canvas.delete("all")
            self.refresh_preview_display()

    def display_preview_image_single(self, pil_img):
        try:
            self.preview_canvas.delete("all")
            canvas_width = self.preview_canvas.winfo_width()
            canvas_height = self.preview_canvas.winfo_height()
            if canvas_width <= 1 or canvas_height <= 1:
                self.root.after(50, lambda: self.display_preview_image_single(pil_img))
                return

            img_width, img_height = pil_img.size
            if img_width <= 0 or img_height <= 0:
                raise ValueError("图像尺寸无效")

            ratio = min(canvas_width / img_width, canvas_height / img_height) * 0.98
            new_width = max(1, int(img_width * ratio))
            new_height = max(1, int(img_height * ratio))
            resize_method = Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.ANTIALIAS
            display_img = pil_img
            if display_img.mode == 'P':
                display_img = display_img.convert('RGBA' if 'transparency' in display_img.info else 'RGB')
            elif display_img.mode == 'L':
                display_img = display_img.convert('RGB')

            resized_img = display_img.resize((new_width, new_height), resize_method)
            self.preview_photo_image = ImageTk.PhotoImage(resized_img)

            x_offset = (canvas_width - new_width) // 2
            y_offset = (canvas_height - new_height) // 2
            self.preview_canvas.create_image(x_offset, y_offset, anchor=tk.NW, image=self.preview_photo_image)

            self.preview_display_info = {
                'type': 'single', 'ratio': ratio, 'x_offset': x_offset, 'y_offset': y_offset,
                'img_width': new_width, 'img_height': new_height,
                'orig_width': img_width, 'orig_height': img_height
            }
            self.redraw_placed_points([0, 1])

        except Exception as e:
            messagebox.showerror("显示预览错误", f"无法显示单页预览: {e}", parent=self.root)
            self.update_status(f"显示预览错误: {e}")

    def display_preview_image_pair(self, pil_img_left, pil_img_right):
        try:
            self.preview_canvas.delete("all")
            canvas_width = self.preview_canvas.winfo_width()
            canvas_height = self.preview_canvas.winfo_height()
            if canvas_width <= 1 or canvas_height <= 1:
                self.root.after(50, lambda: self.display_preview_image_pair(pil_img_left, pil_img_right))
                return

            lw, lh = pil_img_left.size
            rw, rh = pil_img_right.size
            if lw <= 0 or lh <= 0 or rw <= 0 or rh <= 0:
                raise ValueError("图像尺寸无效")

            ratio_w = canvas_width / (lw + rw)
            ratio_h = canvas_height / max(lh, rh)
            ratio = min(ratio_w, ratio_h) * 0.97
            final_lh = max(1, int(lh * ratio))
            final_rh = max(1, int(rh * ratio))
            final_display_height = max(final_lh, final_rh)
            final_lw = int(lw * (final_display_height / lh)) if lh > 0 else 0
            final_rw = int(rw * (final_display_height / rh)) if rh > 0 else 0

            resize_method = Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.ANTIALIAS
            img_left_display = pil_img_left
            if img_left_display.mode == 'P':
                img_left_display = img_left_display.convert('RGBA' if 'transparency' in img_left_display.info else 'RGB')
            elif img_left_display.mode == 'L':
                img_left_display = img_left_display.convert('RGB')
            img_right_display = pil_img_right
            if img_right_display.mode == 'P':
                img_right_display = img_right_display.convert('RGBA' if 'transparency' in img_right_display.info else 'RGB')
            elif img_right_display.mode == 'L':
                img_right_display = img_right_display.convert('RGB')

            resized_left = img_left_display.resize((final_lw, final_display_height), resize_method)
            resized_right = img_right_display.resize((final_rw, final_display_height), resize_method)
            self.preview_photo_image_left = ImageTk.PhotoImage(resized_left)
            self.preview_photo_image_right = ImageTk.PhotoImage(resized_right)

            total_display_width = final_lw + final_rw
            x_offset_total = (canvas_width - total_display_width) // 2
            y_offset_total = (canvas_height - final_display_height) // 2
            x_offset_left = x_offset_total
            y_offset_left = y_offset_total
            x_offset_right = x_offset_total + final_lw
            y_offset_right = y_offset_total

            self.preview_canvas.create_image(x_offset_left, y_offset_left, anchor=tk.NW, image=self.preview_photo_image_left)
            self.preview_canvas.create_image(x_offset_right, y_offset_right, anchor=tk.NW, image=self.preview_photo_image_right)

            self.preview_display_info = {
                'type': 'pair',
                'ratio_left': final_display_height / lh if lh > 0 else 0,
                'ratio_right': final_display_height / rh if rh > 0 else 0,
                'x_offset_left': x_offset_left, 'y_offset_left': y_offset_left,
                'img_width_left': final_lw, 'img_height_left': final_display_height,
                'orig_width_left': lw, 'orig_height_left': lh,
                'x_offset_right': x_offset_right, 'y_offset_right': y_offset_right,
                'img_width_right': final_rw, 'img_height_right': final_display_height,
                'orig_width_right': rw, 'orig_height_right': rh,
            }
            self.redraw_placed_points([0, 1, 2, 3])

        except Exception as e:
            messagebox.showerror("显示预览错误", f"无法显示双页预览: {e}", parent=self.root)
            self.update_status(f"显示预览错误: {e}")

    def redraw_placed_points(self, point_indices_to_draw):
        if not self.preview_display_info:
            return
        point_colors = ["#FF0000", "#FF00FF", "#0000FF", "#00FFFF"]
        point_radius = 5
        for i in point_indices_to_draw:
            if not (0 <= i < 4):
                continue
            relative_y = self.point_positions[i].get()
            if not (0.0 <= relative_y <= 1.0):
                continue
            abs_x, abs_y = -1, -1
            info = self.preview_display_info
            if i in [0, 1] and info.get('type'):
                if info['type'] == 'single' and info.get('ratio', 0) > 1e-9:
                    abs_y = info['y_offset'] + int(info['orig_height'] * relative_y * info['ratio'])
                    abs_x = info['x_offset']
                elif info['type'] == 'pair' and info.get('ratio_left', 0) > 1e-9:
                    abs_y = info['y_offset_left'] + int(info['orig_height_left'] * relative_y * info['ratio_left'])
                    abs_x = info['x_offset_left']
            elif i in [2, 3] and info.get('type') == 'pair' and info.get('ratio_right', 0) > 1e-9:
                abs_y = info['y_offset_right'] + int(info['orig_height_right'] * relative_y * info['ratio_right'])
                abs_x = info['x_offset_right'] + info['img_width_right']
            if abs_x != -1 and abs_y != -1:
                marker_id = self.preview_canvas.create_oval(
                    abs_x - point_radius, abs_y - point_radius,
                    abs_x + point_radius, abs_y + point_radius,
                    fill=point_colors[i % len(point_colors)], outline="white", width=1
                )
                text_id = self.preview_canvas.create_text(abs_x, abs_y, text=f"P{i+1}", fill="white", font=("Arial", 7, "bold"))
                self.point_markers.append(marker_id)
                self.point_markers.append(text_id)

    def _clear_point_markers(self):
        for marker_id in self.point_markers:
            try:
                self.preview_canvas.delete(marker_id)
            except tk.TclError:
                pass
        self.point_markers = []

    def toggle_sampling_mode(self, force_exit=False):
        if force_exit:
            self.is_sampling_mode = True
        if self.is_sampling_mode:
            self.is_sampling_mode = False
            self.sampling_instruction_label.config(text="")
            self.start_sampling_button.config(text="开始手动采样")
            is_point_mode = (self.book_format.get() == "采样点对齐拼版")
            self.clear_sampling_button.config(state=tk.NORMAL if is_point_mode else tk.DISABLED)
            self.root.config(cursor="")
            self.update_status("已退出采样模式。")
        else:
            if self.book_format.get() != "采样点对齐拼版":
                messagebox.showwarning("模式不符", "手动采样点仅在 '采样点对齐拼版' 模式下可用。", parent=self.root)
                return
            if not self.current_preview_pil_image:
                messagebox.showwarning("无图像", "请先加载预览图像再开始采样。", parent=self.root)
                return
            if self.preview_display_info.get('type') != 'pair':
                messagebox.showinfo("需要双页预览", "请确保预览区域显示了左右双页图像再开始采样。", parent=self.root)
                return
            self.is_sampling_mode = True
            self.current_point_to_set = 0
            self.update_sampling_instruction()
            self.start_sampling_button.config(text="退出采样模式")
            self.clear_sampling_button.config(state=tk.NORMAL)
            self.root.config(cursor="crosshair")
            self.update_status("进入采样模式，请按提示点击。")

    def clear_sampling_points(self):
        if self.book_format.get() != "采样点对齐拼版":
            return
        for i in range(4):
            self.point_positions[i].set([0.15, 0.85, 0.15, 0.85][i])
            self.absolute_points[i] = (None, None)
        self._clear_point_markers()
        self.redraw_placed_points([0, 1, 2, 3])
        if self.is_sampling_mode:
            self.current_point_to_set = 0
            self.update_sampling_instruction()
            self.update_status("采样点已清除并恢复默认，请重新开始采样。")
        else:
            self.update_status("采样点已清除并恢复默认。")

    def update_sampling_instruction(self):
        if not self.is_sampling_mode:
            self.sampling_instruction_label.config(text="")
            return
        point_labels = ["P1 (左图左上参考点)", "P2 (左图左下参考点)", "P3 (右图右上参考点)", "P4 (右图右下参考点)"]
        if 0 <= self.current_point_to_set < 4:
            instruction = f"请点击预览图中 '{point_labels[self.current_point_to_set]}' 的位置"
            if self.current_point_to_set >= 2 and self.preview_display_info.get('type') != 'pair':
                instruction += " (请确保预览显示双页)"
        else:
            instruction = "所有 4 个采样点已设置完毕！可退出采样或重新开始。"
        self.sampling_instruction_label.config(text=instruction)

    def on_canvas_click(self, event):
        if not self.is_sampling_mode or self.current_point_to_set >= 4:
            return
        if not self.preview_display_info:
            print("Warning: 无法获取预览显示信息，无法设置采样点。")
            return
        canvas_x, canvas_y = event.x, event.y
        relative_y = -1.0
        abs_coords = (None, None)
        clicked_point_index = self.current_point_to_set
        display_type = self.preview_display_info.get('type')
        try:
            target_img_info = None
            img_key_prefix = ""
            if display_type == 'single' and clicked_point_index < 2:
                x_offset = self.preview_display_info['x_offset']
                y_offset = self.preview_display_info['y_offset']
                disp_w = self.preview_display_info['img_width']
                disp_h = self.preview_display_info['img_height']
                if x_offset <= canvas_x < x_offset + disp_w and y_offset <= canvas_y < y_offset + disp_h:
                    target_img_info = self.preview_display_info
                    img_key_prefix = ""
            elif display_type == 'pair':
                if clicked_point_index < 2:
                    x_offset = self.preview_display_info['x_offset_left']
                    y_offset = self.preview_display_info['y_offset_left']
                    disp_w = self.preview_display_info['img_width_left']
                    disp_h = self.preview_display_info['img_height_left']
                    if x_offset <= canvas_x < x_offset + disp_w and y_offset <= canvas_y < y_offset + disp_h:
                        target_img_info = self.preview_display_info
                        img_key_prefix = "left"
                else:
                    x_offset = self.preview_display_info['x_offset_right']
                    y_offset = self.preview_display_info['y_offset_right']
                    disp_w = self.preview_display_info['img_width_right']
                    disp_h = self.preview_display_info['img_height_right']
                    if x_offset <= canvas_x < x_offset + disp_w and y_offset <= canvas_y < y_offset + disp_h:
                        target_img_info = self.preview_display_info
                        img_key_prefix = "right"
            if target_img_info and img_key_prefix is not None:
                ratio_key = f"ratio{'_' + img_key_prefix if img_key_prefix else ''}"
                orig_h_key = f"orig_height{'_' + img_key_prefix if img_key_prefix else ''}"
                orig_w_key = f"orig_width{'_' + img_key_prefix if img_key_prefix else ''}"
                x_offset_key = f"x_offset{'_' + img_key_prefix if img_key_prefix else ''}"
                y_offset_key = f"y_offset{'_' + img_key_prefix if img_key_prefix else ''}"
                ratio = target_img_info.get(ratio_key, 0)
                orig_h = target_img_info.get(orig_h_key, 0)
                orig_w = target_img_info.get(orig_w_key, 0)
                x_offset = target_img_info.get(x_offset_key, 0)
                y_offset = target_img_info.get(y_offset_key, 0)
                if ratio > 1e-9 and orig_h > 0:
                    orig_y = (canvas_y - y_offset) / ratio
                    orig_x = (canvas_x - x_offset) / ratio
                    relative_y = orig_y / orig_h
                    abs_coords = (orig_x, orig_y)
                    relative_y = max(0.0, min(1.0, relative_y))
                else:
                    relative_y = -1.0
            if 0.0 <= relative_y <= 1.0:
                self.point_positions[clicked_point_index].set(round(relative_y, 4))
                self.absolute_points[clicked_point_index] = abs_coords
                self.redraw_placed_points([clicked_point_index])
                self.current_point_to_set += 1
                self.update_sampling_instruction()
                if self.current_point_to_set >= 4:
                    self.update_status("所有采样点设置完成。")
            else:
                self.update_status("点击位置无效，请点击图像内的参考点。")
        except Exception as e:
            print(f"Error processing canvas click: {e}")
            import traceback
            traceback.print_exc()
            messagebox.showerror("采样错误", f"处理点击时出错:\n{e}", parent=self.root)

    def start_processing(self):
        input_dir = self.input_dir.get()
        output_dir = self.output_dir.get()
        book_name = self.book_name.get().strip()
        if not input_dir or not os.path.isdir(input_dir):
            messagebox.showerror("错误", "请选择有效的输入图片文件夹。", parent=self.root)
            return
        if not output_dir:
            messagebox.showerror("错误", "请选择有效的输出文件夹。", parent=self.root)
            return
        if not book_name:
            messagebox.showerror("错误", "请输入输出书籍/文件名的前缀。", parent=self.root)
            return
        if not self.image_files:
            self.load_image_list()
        if not self.image_files:
            messagebox.showerror("错误", "输入文件夹中没有找到支持的图片文件。", parent=self.root)
            return
        try:
            os.makedirs(output_dir, exist_ok=True)
        except Exception as e:
            messagebox.showerror("错误", f"无法创建输出文件夹:\n{output_dir}\n错误: {e}", parent=self.root)
            return
        try:
            dpi_val = int(self.dpi.get())
            center_margin = int(self.margin_white.get())
            ground_height = int(self.ground_height.get())
            save_format = self.save_format.get().lower()
            book_format_mode = self.book_format.get()
            points_relative_y = [p.get() for p in self.point_positions[0:4]]
            if book_format_mode == "采样点对齐拼版" and not all(0.0 <= y <= 1.0 for y in points_relative_y):
                messagebox.showerror("参数错误", "采样点坐标无效或未设置完整 (Y坐标应在 0.0 到 1.0 之间)。\n请使用手动采样或检查输入。", parent=self.root)
                return
        except ValueError as e:
            messagebox.showerror("参数错误", f"数值设置无效: {e}", parent=self.root)
            return

        self.update_status("开始处理...")
        self.root.config(cursor="watch")
        processed_count = 0
        output_files = []

        try:
            if book_format_mode == "单页拼版":
                processed_count, output_files = process_single_page(
                    input_dir, output_dir, book_name, self.image_files, center_margin, dpi_val, save_format
                )
            elif book_format_mode == "双页拼版":
                processed_count, output_files = process_double_page(
                    input_dir, output_dir, book_name, self.image_files, center_margin, dpi_val, save_format
                )
            elif book_format_mode == "依据装帧拼版":
                processed_count, output_files = process_by_binding_simple(
                    input_dir, output_dir, book_name, self.image_files, center_margin, ground_height, dpi_val, save_format, points_relative_y
                )
            elif book_format_mode == "采样点对齐拼版":
                processed_count, output_files = process_by_points_align_scale(
                    input_dir, output_dir, book_name, self.image_files, center_margin, ground_height, dpi_val, save_format, points_relative_y
                )
            else:
                raise ValueError(f"未知的拼版模式: {book_format_mode}")

            self.update_status(f"处理完成: {processed_count} 个文件已生成。")
            if processed_count > 0:
                messagebox.showinfo("处理完成", f"成功生成 {processed_count} 个输出文件到:\n{output_dir}", parent=self.root)
                if output_files and self.show_processed_image.get():
                    open_file(output_files[0])
            else:
                messagebox.showinfo("处理完成", "处理完成，但未生成任何文件（可能因错误中止）。", parent=self.root)

        except Exception as e:
            self.update_status(f"处理错误: {e}")
            messagebox.showerror("处理错误", f"处理图片时发生错误:\n{e}", parent=self.root)
            import traceback
            traceback.print_exc()
        finally:
            self.root.config(cursor="")


def main():
    root = tk.Tk()
    try:
        style = ttk.Style()
        available_themes = style.theme_names()
        if 'clam' in available_themes:
            style.theme_use('clam')
        elif 'vista' in available_themes:
            style.theme_use('vista')
        elif 'aqua' in available_themes:
            style.theme_use('aqua')
        style.configure("Accent.TButton", font=('SimSun', 10, 'bold'), foreground="white", background="#0078D7")
        style.map("Accent.TButton", background=[('active', '#005A9E')])
    except Exception as e:
        print(f"Info: Could not apply custom theme. Error: {e}")
    app = GujipingyeApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()